using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Schema;
using System.IO;

/* TODO
 * need to check for corrupt xml file
 */

namespace FileFolderNavigator
{
    public class FolderNavigator
    {
        XmlDocument _tree;
        string _rootfolder;
        string _rootpath;

        #region events
        public event EventHandler FolderFound;
        public event EventHandler FileFound;
        public event EventHandler FolderAdded;
        public event EventHandler FileAdded;
        public event EventHandler FolderRemoved;
        public event EventHandler FileRemoved;
        public event EventHandler FileChanged;
        public event EventHandler FolderChanged;
        #endregion

        public XmlDocument Tree
        {
            get { return _tree; }
            set { _tree = value; }
        }

        public string RootFolder
        {
            get { return _rootfolder; }
            set { _rootfolder = value; }
        }

        public string RootPath
        {
            get { return _rootpath; }
            set { _rootpath = value; }
        }

        public string Root
        {
            get { return RootPath + RootFolder; }
        }

        public NavigatorElement RootNavigator
        {
            get { return new NavigatorElement((XmlElement)Tree.FirstChild); }
        }

        

        #region eventhandlers
        private void RegisterEvents()
        {
            FolderRemoved += new EventHandler(FolderNavigator_FolderRemoved);
            FolderChanged += new EventHandler(FolderNavigator_FolderChanged);
            FolderAdded += new EventHandler(FolderNavigator_FolderAdded);
            FolderFound += new EventHandler(FolderNavigator_FolderFound);
            FileRemoved += new EventHandler(FolderNavigator_FileRemoved);
            FileChanged += new EventHandler(FolderNavigator_FileChanged);
            FileAdded += new EventHandler(FolderNavigator_FileAdded);
            FileFound += new EventHandler(FolderNavigator_FileFound);
        }
        void FolderNavigator_FileFound(object sender, EventArgs e)
        {
            
        }

        void FolderNavigator_FileAdded(object sender, EventArgs e)
        {
            
        }

        void FolderNavigator_FileChanged(object sender, EventArgs e)
        {
            
        }

        void FolderNavigator_FileRemoved(object sender, EventArgs e)
        {
            
        }

        void FolderNavigator_FolderFound(object sender, EventArgs e)
        {
            
        }

        void FolderNavigator_FolderAdded(object sender, EventArgs e)
        {
            
        }

        void FolderNavigator_FolderChanged(object sender, EventArgs e)
        {
            
        }

        void FolderNavigator_FolderRemoved(object sender, EventArgs e)
        {

        }
        #endregion

        public FolderNavigator()
        {
            _tree = new XmlDocument();
            RegisterEvents();
        }

        public FolderNavigator(string path)
        {
            Tree = new XmlDocument();
            if (!ReadTree(path))
                throw new Exception("Error occured while loading file");
            _rootpath = Tree.FirstChild.Attributes["name"].Value.Trim();
            _rootfolder = Tree.FirstChild.FirstChild.Attributes["name"].Value.Trim();
            if (RootPath[RootPath.Length - 1] != '\\')
            {
                RootPath += "\\";
            }
            RegisterEvents();
        }

        public FolderNavigator(string folderpath, string foldername)
        {
            RootPath = folderpath.Trim();
            if (RootPath[RootPath.Length - 1] != '\\')
            {
                RootPath += "\\";
            }

            RootFolder = foldername;
            _tree = new XmlDocument();
            RegisterEvents();
        }

        #region later
        public FolderNavigator(XmlDocument old)
        {
            _rootpath = old.FirstChild.Attributes["name"].Value.Trim();
            _rootfolder = old.FirstChild.FirstChild.Attributes["name"].Value.Trim();
            if (RootPath[RootPath.Length - 1] != '\\')
            {
                RootPath += "\\";
            }

            Tree = old;
            RegisterEvents();
        }

        public FolderNavigator(XmlDocument old, string folderpath, string foldername)
        {
            //implement later
        }
        #endregion

        #region next
        public void UpdateTree()
        {
            XmlElement proot = (XmlElement)Tree.FirstChild;
            XmlElement root = (XmlElement)proot.FirstChild;
            NavigatorElement nroot = new NavigatorElement(root);

            Queue<NavigatorElement> queue = new Queue<NavigatorElement>();
            queue.Enqueue(nroot);
            while (queue.Count > 0)
            {
                NavigatorElement cur = queue.Dequeue();
                string curpath = cur.Path;
                cur.Checked();
                string[] dirs = Directory.GetDirectories(curpath);
                string[] files = Directory.GetFiles(curpath);

                foreach (string file in files)
                {
                    string[] t = file.Split('\\');
                    string fn = t[t.Length - 1];
                    DateTime lastaltered = File.GetLastWriteTime(file);
                    NavigatorElement cfn = cur.FindChildFile(fn);
                    if (cfn == null)
                    {
                        cfn = cur.AddFile(fn, lastaltered);
                        cfn.SetDirty();
                        //OnFileFound(this, new NavigatorEventArgs(cfn, EventType.FileFound));
                        OnFileAdded(this, new NavigatorEventArgs(cfn, EventType.FileAdded));
                    }
                    else
                    {
                        if (cfn.Altered.CompareTo(DateTime.Parse(lastaltered.ToString())) != 0)
                        {
                            cfn.Altered = lastaltered;
                            cfn.SetDirty();
                            OnFileChanged(this, new NavigatorEventArgs(cfn, EventType.FileChanged));
                        }
                    }
                    cfn.Checked();
                }

                foreach (string dir in dirs)
                {
                    string[] t = dir.Split('\\');
                    string dn = t[t.Length - 1];
                    NavigatorElement cdn = cur.FindChildFolder(dn);
                    if (cdn == null)
                    {
                        cdn = cur.AddFolder(dn);
                        cdn.SetDirty();
                        //OnFolderFound(this, new NavigatorEventArgs(cdn, EventType.FolderFound));
                        OnFolderAdded(this, new NavigatorEventArgs(cdn, EventType.FolderAdded));
                    }
                    cdn.Checked();
                    queue.Enqueue(cdn);
                }
            }

            XmlNodeList removedfolders = Tree.FirstChild.SelectNodes(".//Folder[not(@checked) or @checked='false']");
            XmlNodeList removedfiles = Tree.FirstChild.SelectNodes(".//File[not(@checked)]");

            foreach (XmlElement elem in removedfiles)
            {
                NavigatorElement f = new NavigatorElement(elem);
                OnFileRemoved(this, new NavigatorEventArgs(new NavigatorElement(elem), EventType.FileRemoved));
                f.DeleteSelf();
            }

            foreach (XmlElement elem in removedfolders)
            {
                NavigatorElement f = new NavigatorElement(elem);
                OnFolderRemoved(this, new NavigatorEventArgs(new NavigatorElement(elem), EventType.FolderRemoved));
            }

            foreach (XmlElement elem in removedfolders)
            {
                if (elem != null)
                {
                    try
                    {
                        NavigatorElement f = new NavigatorElement(elem);
                        f.DeleteAllChildren();
                        f.DeleteSelf();
                    }
                    catch (Exception) { }
                }
            }
        }

        public void CompareTree(FolderNavigator other)
        {

        }
        #endregion

        #region read/write to/from file
        public bool WriteTree(string path)
        {
            try
            {
                string str = Tree.InnerXml;
                File.WriteAllText(path, str);
            }
            catch (Exception e)
            {
                return false;
            }
            return true;
        }

        public bool ReadTree(string path)
        {
            TextReader reader = (TextReader)File.OpenText(path);
            string str = reader.ReadToEnd();
            try
            {
                Tree.Load(File.OpenText(path));
            }
            catch (Exception e)
            {
                return false;
            }
            return true;
        }

        public bool ReadXmlString(string xml)
        {
            try
            {
                Tree.LoadXml(xml);
            }
            catch (Exception e)
            {
                return false;
            }
            return true;
        }
        #endregion

        public void BuildTree()
        {
            XmlElement proot = CreateFolder(this.RootPath);
            Tree.AppendChild(proot);
            XmlElement root = CreateFolder(this.RootFolder);
            proot.AppendChild(root);
            Queue<XmlElement> queue = new Queue<XmlElement>();
            string[] dirs = Directory.GetDirectories(Root);
            foreach (string dir in dirs)
            {
                string[] t = dir.Split('\\');
                string dd = t[t.Length - 1];
                XmlElement d = CreateFolder(dd);
                root.AppendChild(d);
                queue.Enqueue(d);
                OnFolderFound(this, (EventArgs)(new NavigatorEventArgs(new NavigatorElement(d), EventType.FolderFound)));
            }

            string[] files = Directory.GetFiles(Root);
            foreach (string file in files)
            {
                string[] t = file.Split('\\');
                string ff = t[t.Length - 1];
                DateTime lastaltered = File.GetLastWriteTime(file);
                XmlElement f = CreateFile(ff,lastaltered);
                root.AppendChild(f);
                OnFileFound(this, (EventArgs)(new NavigatorEventArgs(new NavigatorElement(f), EventType.FileFound)));
            }

            while (queue.Count > 0)
            {
                XmlElement cur = queue.Dequeue();
                string path = BuildPath(cur);

                dirs = Directory.GetDirectories(path);
                foreach (string dir in dirs)
                {
                    string[] t = dir.Split('\\');
                    string dd = t[t.Length - 1];
                    XmlElement d = CreateFolder(dd);
                    cur.AppendChild(d);
                    queue.Enqueue(d);
                    OnFolderFound(this, (EventArgs)(new NavigatorEventArgs(new NavigatorElement(d), EventType.FolderFound)));
                }

                files = Directory.GetFiles(path);
                foreach (string file in files)
                {
                    string[] t = file.Split('\\');
                    string ff = t[t.Length - 1];
                    DateTime lastaltered = File.GetLastWriteTime(file);
                    XmlElement f = CreateFile(ff, lastaltered); 
                    cur.AppendChild(f);
                    OnFileFound(this, (EventArgs)(new NavigatorEventArgs(new NavigatorElement(f), EventType.FileFound)));
                }
            }
        }

        private XmlElement CreateFolder(string name)
        {
            XmlElement elem = Tree.CreateElement("Folder");
            elem.SetAttribute("name", name);
            return elem;
        }
        private XmlElement CreateFile(string name, DateTime lastaltered)
        {
            XmlElement elem = Tree.CreateElement("File");
            elem.SetAttribute("name", name);
            elem.SetAttribute("altered", lastaltered.ToString());
            return elem;
        }
        private String BuildPath(XmlElement elem)
        {
            XmlElement p = elem;
            string path = "\\"+elem.GetAttribute("name");
            p = (XmlElement)p.ParentNode;
            while (p != null)
            {
                path = "\\" + p.GetAttribute("name") + path;
                if (!(p.ParentNode is XmlDocument))
                {
                    p = (XmlElement)p.ParentNode;
                }
                else
                {
                    p = null;
                }
            }
            return path.Substring(1);
        }

        #region onevents
        protected void OnFolderFound(object sender, EventArgs args) { if (FolderFound != null) FolderFound(sender, args); }
        protected void OnFileFound(object sender, EventArgs args) { if (FileFound != null) FileFound(sender, args); }
        protected void OnFolderAdded(object sender, EventArgs args) { if (FolderAdded != null) FolderAdded(sender, args); }
        protected void OnFileAdded(object sender, EventArgs args) { if (FileAdded != null) FileAdded(sender, args); }
        protected void OnFolderRemoved(object sender, EventArgs args) { if (FolderRemoved != null) FolderRemoved(sender, args); }
        protected void OnFileRemoved(object sender, EventArgs args) { if (FileRemoved != null) FileRemoved(sender, args); }
        protected void OnFileChanged(object sender, EventArgs args) { if (FileChanged != null) FileChanged(sender, args); }
        protected void OnFolderChanged(object sender, EventArgs args) { if (FolderChanged != null) FolderChanged(sender, args); }
        #endregion
    }

    public class NavigatorElement
    {
        XmlElement _elem;
        
        public NavigatorElement(XmlElement elem)
        {
            _elem = elem;
        }
        public string Name
        {
            get { return _elem.GetAttribute("name"); }
        }
        public string Path
        {
            get
            {
                XmlElement p = _elem;
                string path = "\\" + _elem.GetAttribute("name");
                p = (XmlElement)p.ParentNode;
                while (p != null)
                {
                    path = "\\" + p.GetAttribute("name") + path;
                    if (!(p.ParentNode is XmlDocument))
                    {
                        p = (XmlElement)p.ParentNode;
                    }
                    else
                    {
                        p = null;
                    }
                }
                return path.Substring(1);
            }
        }
        public string Type
        {
            get { return _elem.Name; }
        }
        public DateTime Altered
        {
            get { if (Type == "File") return DateTime.Parse(_elem.GetAttribute("altered")); else return DateTime.Now; }
            set { _elem.SetAttribute("altered", value.ToString()); }
        }
        public NavigatorElement Parent
        {
            get
            {
                return new NavigatorElement((XmlElement)_elem.ParentNode);
            }
        }
        public NavigatorElement FirstChild
        {
            get { return new NavigatorElement((XmlElement)_elem.FirstChild); }
        }
        public NavigatorElement NextSibling
        {
            get { return new NavigatorElement((XmlElement)_elem.NextSibling); }
        }
        public NavigatorElement[] ChildFolders
        {
            get
            {
                XmlNodeList nodes = _elem.SelectNodes("./Folder");
                NavigatorElement[] folders = new NavigatorElement[nodes.Count];
                int i = 0;
                foreach (XmlElement elem in nodes)
                {
                    folders[i] = new NavigatorElement(elem);
                    i++;
                }
                return folders;
            }
        }
        public NavigatorElement[] ChildFiles
        {
            get
            {
                XmlNodeList nodes = _elem.SelectNodes("./File");
                NavigatorElement[] files = new NavigatorElement[nodes.Count];
                int i = 0;
                foreach (XmlElement elem in nodes)
                {
                    files[i] = new NavigatorElement(elem);
                    i++;
                }
                return files;
            }
        }
        public NavigatorElement[] Children
        {
            get
            {
                XmlNodeList nodes = _elem.ChildNodes;
                NavigatorElement[] children = new NavigatorElement[nodes.Count];
                int i = 0;
                foreach (XmlElement elem in nodes)
                {
                    children[i] = new NavigatorElement(elem);
                    i++;
                }
                return children;
            }
        }
        public NavigatorElement AddFile(string name,DateTime lastaltered)
        {
            XmlElement elem = _elem.OwnerDocument.CreateElement("File");
            elem.SetAttribute("name", name);
            elem.SetAttribute("altered", lastaltered.ToString());
            _elem.AppendChild(elem);
            return new NavigatorElement(elem);
        }
        public NavigatorElement AddFolder(string name)
        {
            XmlElement elem = _elem.OwnerDocument.CreateElement("Folder");
            elem.SetAttribute("name", name);
            _elem.AppendChild(elem);
            return new NavigatorElement(elem);
        }
        public void DeleteAllChildren()
        {
            _elem.RemoveAll();
        }
        public void DeleteSelf()
        {
            _elem.ParentNode.RemoveChild((XmlNode)_elem);
        }
        public NavigatorElement FindChildFile(string name)
        {
            XmlElement elem = (XmlElement)_elem.SelectSingleNode("./File[@name='"+name+"']");
            if (elem != null)
            {
                return new NavigatorElement(elem);
            }
            else
            {
                return null;
            }
        }
        public NavigatorElement FindChildFolder(string name)
        {
            XmlElement elem = (XmlElement)_elem.SelectSingleNode("./Folder[@name='"+name+"']");
            if (elem != null)
            {
                return new NavigatorElement(elem);
            }
            else
            {
                return null;
            }
        }
        public void SetDirty()
        {
            _elem.SetAttribute("dirty", "true");
        }
        public void Checked()
        {
            _elem.SetAttribute("checked", "true");
        }
        public string SubstitutePath(string path, string name)
        {
            XmlElement proot = (XmlElement)_elem.OwnerDocument.FirstChild;
            XmlElement root = (XmlElement)proot.FirstChild;
            string p = proot.GetAttribute("name");
            string r = root.GetAttribute("name");

            proot.SetAttribute("name", path);
            root.SetAttribute("name", name);

            string ret = this.Path;

            proot.SetAttribute("name", p);
            root.SetAttribute("name", r);
            return ret;
        }
    }
}
